using System;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;

namespace TreeEx
{
	/// <summary>
	/// Summary description for NodeEx.
	/// </summary>
	public class NodeEx 
	{
		#region Public Node properties

		public static StringFormat	DefaultFormat	= new StringFormat(StringFormatFlags.DirectionVertical);
		
		public const int	NODE_WIDTH		= 10;
		public const int	NODE_HEIGHT		= 10;
		public const int	LEVEL_MARGIN	= 100;

		public string		Key;
		public string		Text;
		public StringFormat TextFormat;

		public int			X;
		public int			Y;
		public int			NodeWidth;
		public int			NodeHeight;
		public int			TotalWidth;
		public int			TotalHeight;
		public int			Level		= 0;
		public int			LevelMargin	= LEVEL_MARGIN;

		public bool			ShowLabels	= true;
		public bool			ShowLines	= true;
		public bool			Expand		= true;
        public bool         Highlighted = false;

		public NodeEx		Parent		= null;
		public Brush		SelectBrush	= null;

		#endregion
		
		#region private members and constructors

		ArrayList			_Nodes;
		HybridDictionary	_NodeLookup;
		
		public NodeEx(string nodeText)
		{
			Initialize(null, nodeText);	
		}

		public NodeEx(string key, string nodeText)
		{
			Initialize(key, nodeText);	
		}

		protected void Initialize(string key, string nodeText)
		{
			Key			= key;
			Text		= nodeText;
			NodeWidth	= NODE_WIDTH;
			NodeHeight	= NODE_HEIGHT;
			X			= 10;
			Y			= 10;
		}

		#endregion

		#region Public methods

		/// <summary>
		/// Get Node at given point (X,Y). Returns null if not found
		/// Recursive function - checks all childs nodes recursively
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		public NodeEx GetNodeAt(int x, int y)
		{
			if(	this.X >= x - this.NodeWidth	&& this.X <= x + this.NodeWidth &&
				this.Y >= y - this.NodeHeight	&& this.Y <= y + this.NodeHeight)
				return this;

			if(_Nodes != null)
			{
				foreach(NodeEx node in _Nodes)
				{
					NodeEx find = node.GetNodeAt(x, y);
					if(find != null)
						return find;
				}
			}

			return null;
		}


		/// <summary>
		/// Add new node object to child nodes
		/// </summary>
		/// <param name="node"></param>
		public void Add(NodeEx node)
		{
			if(node == null)
				return;

			if(_Nodes == null)
				_Nodes = new ArrayList();

			_Nodes.Add(node);

			if(node.Key != null)
			{
				if(_NodeLookup == null)
					_NodeLookup = new HybridDictionary(5, false);

				_NodeLookup.Add(node.Key, node);
			}
		}

		/// <summary>
		/// Remove Child node
		/// </summary>
		/// <param name="gph"></param>
		/// <param name="drawBrush"></param>
		/// <param name="drawPen"></param>
		/// <param name="drawFont"></param>
		public void Remove(string key)
		{
			if(_NodeLookup.Contains(key))
			{
				_Nodes.Remove(_NodeLookup[key]);
				_NodeLookup.Remove(key);
			}
		}

        public void Highlight(bool light)
        {
            Highlighted = light;
        }

        public void Highlight()
        {
            Highlight(true);
        }

        private NodeEx FindNode(string key, NodeEx node)
        {
            NodeEx ret;
            if (node._Nodes != null)
                foreach (NodeEx n in node._Nodes)
                {
                    if (n.Key == key)
                        return n;
                    ret = FindNode(key, n);
                    if (ret != null)
                        return ret;
                }

            return null;
        }

        public NodeEx FindNode(string key)
        {
            if (this.Key == key)
                return this;
            return FindNode(key, this);
        }

        private void HighlightNodes(NodeEx node, bool light)
        {
            if (node._Nodes != null)
                foreach (NodeEx n in node._Nodes)
                {
                    HighlightNodes(n, light);
                }
            Highlighted = light;
        }

        public void HighlightAll(bool light)
        {
            HighlightNodes(this, light);
        }
    

		/// <summary>
		/// Draw current node and all child nodes based on configuration
		/// </summary>
		/// <param name="gph"></param>
		/// <param name="drawBrush"></param>
		/// <param name="drawPen"></param>
		/// <param name="drawFont"></param>
		public void DrawTree(Graphics gph, Brush drawBrush, Pen drawPen, Font drawFont)
		{
			DrawNode(gph, drawBrush);

			DrawLabels(gph, drawBrush, drawFont);

			DrawLines(gph, drawBrush, drawPen, drawFont);

			if(Expand && _Nodes != null)
			{
				Initialize();

				foreach(NodeEx node in _Nodes)
					node.DrawTree(gph, drawBrush, drawPen, drawFont); 
			}
		}

		#endregion

		#region protected/private functions

		protected void Initialize()
		{
			if(_Nodes != null)
			{
				int chY				= Y + LevelMargin;
				int subwidth		= this.TotalWidth/(_Nodes.Count);
				for(int i=0; i < _Nodes.Count; i++)
				{
					((NodeEx)_Nodes[i]).X			= this.X - this.TotalWidth/2 + (subwidth * (i+1)) - subwidth/2;
					((NodeEx)_Nodes[i]).TotalWidth	= subwidth;
					((NodeEx)_Nodes[i]).Parent		= this;
					((NodeEx)_Nodes[i]).Y			= chY;
					((NodeEx)_Nodes[i]).Level		= this.Level + 1;

					// These are global settings at Root level - will be same for all child nodes
					((NodeEx)_Nodes[i]).ShowLabels	= this.ShowLabels;
					((NodeEx)_Nodes[i]).ShowLines	= this.ShowLines;
					((NodeEx)_Nodes[i]).SelectBrush	= this.SelectBrush;
					((NodeEx)_Nodes[i]).TextFormat	= this.TextFormat;
					((NodeEx)_Nodes[i]).NodeHeight	= this.NodeHeight;
					((NodeEx)_Nodes[i]).NodeWidth	= this.NodeWidth;
					((NodeEx)_Nodes[i]).LevelMargin	= this.LevelMargin;

					((NodeEx)_Nodes[i]).Initialize();
				}
			}
		}

		protected void DrawNode(Graphics gph, Brush drawBrush)
		{            
            if (!Highlighted)
            {
                if (this.Expand)
                    gph.FillEllipse(SelectBrush != null ? SelectBrush : drawBrush, X - NodeWidth / 2, Y - NodeHeight / 2, NodeWidth + 2, NodeHeight + 2);
                else
                    gph.FillEllipse(drawBrush, X - NodeWidth / 2, Y - NodeHeight / 2, NodeWidth, NodeHeight);
            }
            else
            {
                if (this.Expand)
                    gph.FillEllipse(Brushes.Lime, X - NodeWidth / 2, Y - NodeHeight / 2, NodeWidth + 2, NodeHeight + 2);
                else
                    gph.FillEllipse(Brushes.Lime, X - NodeWidth / 2, Y - NodeHeight / 2, NodeWidth, NodeHeight);
            }
		}

		protected void DrawLabels(Graphics gph, Brush drawBrush, Font drawFont)
		{
			if(ShowLabels)
				gph.DrawString(Text, drawFont, drawBrush, this.X, this.Y, TextFormat == null ? DefaultFormat : TextFormat);
		}

		protected void DrawLines(Graphics gph, Brush drawBrush, Pen drawPen, Font drawFont)
		{
			if(ShowLines && this.Parent != null)
				gph.DrawLine(drawPen, new Point(this.X, this.Y), new Point(this.Parent.X, this.Parent.Y));
		}

		#endregion
	}
}
